package spec.ocl_mpg;
import java.util.ArrayList;
import java.util.LinkedList;
//import java.util.HashMap;

import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EModelElement;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.impl.EClassImpl;
import org.eclipse.emf.ecore.impl.EGenericTypeImpl;
import org.eclipse.emf.ecore.impl.EOperationImpl;
import org.eclipse.ocl.ecore.Constraint;
import org.eclipse.ocl.ecore.impl.*;

import testcasegenerator.clp.classlevel.MethodPredicate;




public class OCLTree {
	
	// field
	private Constraint constraint;
	private String constraintType;
	private String className;
	private String methodName;
	private ArrayList<String[]> var_list;
	private ArrayList<String[]> param_list;
	private String returnType;
	private TreeNode tree;
	
	private ArrayList<String[]> def_list = new ArrayList<String[]>();
	private ArrayList<String[]> use_list = new ArrayList<String[]>();

	// construct
	public OCLTree(Constraint con) {
		this.constraint = con;
		this.constraintType = constraint.getStereotype();
		this.className = constraint.getSpecification().getContextVariable().getType().getName();

		if (constraint.getConstrainedElements().get(0).getClass().equals(EClassImpl.class)) {
			this.methodName = ((EClassImpl) constraint.getConstrainedElements().get(0)).getName();
		} else if (constraint.getConstrainedElements().get(0).getClass().equals(EOperationImpl.class)) {
			this.methodName = ((EOperationImpl) constraint.getConstrainedElements().get(0)).getName();
		}
		
		if (constraint.getSpecification().getResultVariable() != null) {
			this.returnType = constraint.getSpecification().getResultVariable().getType().getName();
		}
		
		this.tree = buildTree((EObject) constraint.getSpecification().getBodyExpression());
		//this.setDefUse();
		
	}

	// method
	private TreeNode buildTree(EObject obj) {
		
		TreeNode node = null;
	
		// OCL Constraint Type
		if (obj instanceof EGenericTypeImpl) {
			return null;
		} else if (obj instanceof OperationCallExpImpl) {
			node = buildTree((OperationCallExpImpl) obj); 
		} else if (obj instanceof IfExpImpl) {
			node = buildTree((IfExpImpl) obj);
		} else if (obj instanceof PropertyCallExpImpl) {
			node = new TreeNode();
			node.setExpType(ExpType._Prop);
			node.setValue(((PropertyCallExpImpl) obj).getReferredProperty().getName());
			if (((PropertyCallExpImpl) obj).isMarkedPre()) {
				node.setValue(node.getValue() + "@pre");
			}
		} else if (obj instanceof RealLiteralExpImpl) {
			System.err.println("RealLiteralExpImpl isn't implement !");
		} else if (obj instanceof StateExpImpl) {
			System.err.println("StateExpImpl isn't implement !");
		} else if (obj instanceof StringLiteralExpImpl) {
			node = new TreeNode();
			node.setExpType(ExpType._Str);
			node.setValue(((StringLiteralExpImpl) obj).getStringSymbol());
		} else if (obj instanceof IntegerLiteralExpImpl) {
			node = new TreeNode();
			node.setExpType(ExpType._Int);
			node.setValue(Integer.toString(((IntegerLiteralExpImpl) obj).getIntegerSymbol()));
		} else if (obj instanceof BooleanLiteralExpImpl) {
			node = new TreeNode();
			node.setExpType(ExpType._Bool);
			node.setValue(Boolean.toString(((BooleanLiteralExpImpl) obj).getBooleanSymbol()));
		} else if (obj instanceof NullLiteralExpImpl) {
			node = new TreeNode();
			node.setExpType(ExpType._Null);
			node.setValue(((NullLiteralExpImpl) obj).getName());
		} else if (obj instanceof TupleLiteralExpImpl) {
			System.err.println("TupleLiteralExpImpl isn't implement !");
		} else if (obj instanceof TypeExpImpl) {
			System.err.println("TypeExpImpl isn't implement !");
		} else if (obj instanceof UnlimitedNaturalLiteralExpImpl) {
			System.err.println("UnlimitedNaturalLiteralExpImpl isn't implement !");
		} else if (obj instanceof UnspecifiedValueExpImpl) {
			System.err.println("UnspecifiedValueExpImpl isn't implement !");
		} else if (obj instanceof VariableExpImpl) {
			node = new TreeNode();
			node.setExpType(ExpType._Var);
			node.setValue(((VariableExpImpl) obj).getName());
			node.setDataType(((VariableExpImpl) obj).getType().getName());
		} else {
			System.err.println("The type of object is unknow !");
		}
		return node;
	}
	
	// overloading
	private TreeNode buildTree(OperationCallExpImpl exp) {
		TreeNode node = new TreeNode();
		node.setExpType(ExpType._OperExp);
		TreeNode middle = new TreeNode();
		middle.setValue(exp.getReferredOperation().getName());
		middle.setExpType(ExpType._Op);
		node.setMiddleNode(middle);
		
		EList<EObject> list = exp.eContents();
		
		for (EObject obj : list) {
			if (obj instanceof EGenericTypeImpl) {
				continue;
			} else {
				if (node.getLeftNode() == null) {
					node.setLeftNode(buildTree(obj));
				} else {
					node.setRightNode(buildTree(obj));
				}
			}
		}
		
		return node;
	}
	
	private TreeNode buildTree(IfExpImpl exp) {
		TreeNode node = new TreeNode();
		node.setExpType(ExpType._IfExp);
		
		EList<EObject> list = exp.eContents();
		
		for (EObject obj : list) {
			if (obj instanceof EGenericTypeImpl) {
				continue;
			} else {
				if (node.getLeftNode() == null) {
					node.setLeftNode(buildTree(obj));
				} else if (node.getMiddleNode() == null) {
					node.setMiddleNode(buildTree(obj));
				} else {
					node.setRightNode(buildTree(obj));
				}
			}
		}
		
		return node;
	}
	
	public TreeNode getTree() {
		return tree;
	}
	
	public String getConstraintType() {
		return constraintType;
	}

	public String getClassName() {
		return className;
	}

	public String getMethodName() {
		return methodName;
	}
	
	public ArrayList<String[]> getVar_list() {
		return var_list;
	}
	
	public void setVar_list(ArrayList<String[]> varList) {
		var_list = varList;
	}
	
	public ArrayList<String[]> getParam_list() {
		return param_list;
	}

	public void setParam_list(ArrayList<String[]> paramList) {
		param_list = paramList;
	}

	public String getReturnType() {
		return returnType;
	}

	public String toString() {
		return tree.toString();
	}
	
	public void showInformation(){
		System.out.println(constraint);
		System.out.println("constraintType :"+constraintType);
		System.out.println("className :" +className);
		System.out.println("methodName :"+methodName);
		
		if(var_list.size()==0)
			System.out.println("no var");
		else{
		for(int i=0;i<=var_list.size()-1;i++){		
		      System.out.println("var :"+var_list.get(i)[0]);
		      System.out.println("vartype :"+var_list.get(i)[1]);			
		}
		}
		
		if(param_list.size()==0)
			System.out.println("no param");
		else{
		for(int i=0;i<=param_list.size()-1;i++){		
		      System.out.println("param :"+param_list.get(i)[0]);
		      System.out.println("paramtype :"+param_list.get(i)[1]);
		}
		}
		
		
		for(int i=0;i<=def_list.size()-1;i++){		
		      System.out.println("def :"+def_list.get(i)[0]);
		      System.out.println("deftype :"+def_list.get(i)[1]);
		}	
		
		
		for(int i=0;i<=use_list.size()-1;i++){		
		      System.out.println("use :"+use_list.get(i)[0]);
		      System.out.println("usetype :"+use_list.get(i)[1]);
		}	
		
		
	}
	
	/*
	 * each constraint set def_list and use_list 
	 */
	public void setDefUse(){
		
		for(int i=0;i<=var_list.size()-1;i++){
			String constraint = this.toString();
			if(this.constraintType.equals("invariant")){
				//invariant  has no define,use
			}
			else if(this.constraintType.equals("postcondition")){
				if(constraint.contains(" and ")){
					String[] PartString = constraint.split(" and ");
					for(int j=0;j<=PartString.length-1;j++){
						if(PartString[j].contains(" = ")){
							String[] PartString3 = PartString[j].split(" = ");
							if(PartString3[0].contains(var_list.get(i)[0])){
								def_list.add(var_list.get(i));
							}
		                    if(PartString3[1].contains(var_list.get(i)[0])){
								use_list.add(var_list.get(i));
							}
		                    
						}		
					}
				}
				
				else{
					
						String[] PartString = constraint.split(" = ");
						
						if(PartString[0].contains(var_list.get(i)[0])){
							def_list.add(var_list.get(i));
						}
	                    if(PartString[1].contains(var_list.get(i)[0])){
							use_list.add(var_list.get(i));
						}
	                    
					
				}
			}
			//precondition
			else{
                if(constraint.contains(var_list.get(i)[0])){
					use_list.add(var_list.get(i));
				}
				
			}
		}	
	}
	
	public ArrayList<String[]> getDef_list(){
		return def_list;
	}
	public ArrayList<String[]> getUse_list(){
		return use_list;
	}
	
	/*
	 * convert to MethodPredicate
	 */
	public MethodPredicate converttoCLP(){
		MethodPredicate predicate = new MethodPredicate();
		predicate.setclassName(this.className);
		predicate.setevent(this.methodName);
		predicate.setvar_list(this.var_list);
		predicate.setparam_list(this.param_list);
		predicate.setreturnType(returnType);
		return predicate;
	}
	
}
